package main




import "fmt"
// 简单工厂设计模式

// 工厂模式
// 工厂模式（Factory Pattern）是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式，它提供了一种创建对象的最佳方式。
// 在工厂模式中，我们在创建对象时不会对客户端暴露创建逻辑，并且是通过使用一个共同的接口来指向新创建的对象。
// 介绍
// 意图：定义一个创建对象的接口，让其子类自己决定实例化哪一个工厂类，工厂模式使其创建过程延迟到子类进行。
// 主要解决：主要解决接口选择的问题。
// 何时使用：我们明确地计划不同条件下创建不同实例时。
// 如何解决：让其子类实现工厂接口，返回的也是一个抽象的产品。
// 关键代码：创建过程在其子类执行。
// 应用实例： 1、您需要一辆汽车，可以直接从工厂里面提货，而不用去管这辆汽车是怎么做出来的，以及这个汽车里面的具体实现。 2、Hibernate 换数据库只需换方言和驱动就可以。
// 优点： 1、一个调用者想创建一个对象，只要知道其名称就可以了。 2、扩展性高，如果想增加一个产品，只要扩展一个工厂类就可以。 3、屏蔽产品的具体实现，调用者只关心产品的接口。
// 缺点：每次增加一个产品时，都需要增加一个具体类和对象实现工厂，使得系统中类的个数成倍增加，在一定程度上增加了系统的复杂度，同时也增加了系统具体类的依赖。这并不是什么好事。
// 使用场景： 1、日志记录器：记录可能记录到本地硬盘、系统事件、远程服务器等，用户可以选择记录日志到什么地方。 2、数据库访问，当用户不知道最后系统采用哪一类数据库，以及数据库可能有变化时。 3、设计一个连接服务器的框架，需要三个协议，"POP3"、"IMAP"、"HTTP"，可以把这三个作为产品类，共同实现一个接口。
// 注意事项：作为一种创建类模式，在任何需要生成复杂对象的地方，都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式，而简单对象，特别是只需要通过 new 就可以完成创建的对象，无需使用工厂模式。如果使用工厂模式，就需要引入一个工厂类，会增加系统的复杂度。

// simplefactory
type API interface{
	Say(string)
}

// 中间者
type apiSay struct{}

// 对外调用
func NewApiSay(sl string) API{
// 返回接口就是解耦成无状态的
	switch sl {
	case "1":
		//
		return &hiSay{}
	case "2":
		//
		return &helloSay{}
	default:
		// nothing
		return &apiSay{}
	}
	
}
func (*apiSay)Say(name string){
}

type hiSay struct{}
type helloSay struct{}

func (*hiSay)Say(name string){
	fmt.Printf("%s is say hi....\n",name)
}
func (*helloSay)Say(name string){
	fmt.Printf("%s is say hello....\n",name)
}

// ---------------------------------场景1-----------------------------------------
// 工厂生产的都是 类似的产品，比如汽车，汽车工厂只会生产汽车，那么他生产不了轮船，所以生产的汽车类型也很多
// 比如:BenChi BMW LineLoad 等 
// 设计分析: 工厂用于生产的功能 manufacture[制造]===>obj factory[工厂]
// 他们都是汽车Car ==> benchi|bmw|lineload ...等等
// 也就是说newCar 的话得到某一个辆车，那么客户如何得到自己想要的车呢，需要客户说出自己想要的车
// ok 那么来实现

// 定义工厂
type factory interface{
	// 工厂方法
	Run()
}
// 定义生产类
type car struct{}
// 定义生产对象
func NewCar(car_name string) factory{
	// 返回接口解耦成无状态的，这里为什么不返回car ,一旦返回car 的话那么就变成car的一个状态限定了
	// 当然返回car也可以做成无状态的解耦，需要嵌套接口实现，后面会有细说
	// 根据客户选择返回实例对象
	// 选择器 | 这里如果担心大小写敏感的话，可以全部转成大写，然后匹配，根据个人爱好，
	switch car_name {
	case "BenChi":
		return &BenChi{} // 这里实例化一个生产对象，该对象匹配是工厂接口，那么该对象必须要实现工厂方法，应为是工厂生产实例化的对象
	case "BMW":
		return &BMW{}
	case "LineLoad":
		return &LineLoad{}
	default:
		return &car{} // 返回一个类当然是处理用户输入非法的问题，让用户能得到友善的提示
	}
}
func (*car)Run(){
	fmt.Println("这里是生产汽车的。。。。")
}

// 实现实例对象
type BenChi struct{}
type BMW struct{}
type LineLoad struct{}

func (*BenChi)Run(){
	fmt.Println("benchi is running")
}

func (*BMW)Run(){
	fmt.Println("bmw is running")
}

func (*LineLoad)Run(){
	fmt.Println("lineload is running")
}

// -------------------------------场景2-------------------------------------------
// 现在项目有个需求，比如数据库，目前使用的数据库是mysql,但是后面可能会用到redis 或者其他的数据库等
// 分析:
// 说明后面可以拓展，其次每一个数据库都是一个实例的对象,还有就是数据库如果存在的话是否考虑复用[简单说就是一个db池来维护]
// 通俗来说就是减少db建立的链接，该功能在此不实现，自己可以根据情况来实现，比如用一个map或者chain来维护这个jdbc
// 工厂根据用户传入的参数来确定生产的对象

type DB interface{
	Connect() bool
}
type jdbc struct{}
func (*jdbc)Connect() bool{
	return false
}
// 这里参数是固定的，当然会有人说为什么不适用选项设计模式，
// 这里说明一下，设计模式根据不同的场景不同的人来使用结果是不一样的，只要自己理解了，能在项目中使用的相对性的方便就可以了
// 没有一个设计模式没有缺陷的
func NewJDBC(sql,url,username,password,port string) DB{
	// 还是一个选项器匹配，当然if 也行这里美观一下
	switch sql {
	case "mysql":
		//
		return &mysql{
			sql:sql,
			url:url,
			username:username,
			password:password,
			port:port,
		}
	case "redis":
		//
		return &redis{
			sql:sql,
			url:url,
			username:username,
			password:password,
			port:port,
		}
	default:
		//其他
		return &jdbc{}
	}
}
type mysql struct{
	sql,url,username,password,port string
}
type redis struct{
	sql,url,username,password,port string
}
func (*mysql)Connect() bool {
	// 建立连接....
	fmt.Println("建立mysql连接.....")
	return true
}
// 下面可以有自己的mysql的方法
func (*redis)Connect() bool {
	// 建立连接....
	fmt.Println("建立redis连接.....")
	return true
}


// ------------------------------场景3--------------------------------------------
// 通过上面的案例，简单可以知道该模式的场景，用在比较大的地方，所有大的地方 就是该对象使用的范围较大，
// 比如我把该对象放在上下文，放在一个工具包里面提供调用
// 该场景是服务注册和服务发现
type Servicer interface{
	GetService(name string) server // 获取服务
}
// 服务生产工厂
type server interface{
	Run() 
}

type mapServices struct{
	// 服务集合
	services map[string]server
}

func NewMapServices() *mapServices{
	return &mapServices{
		services:make(map[string]server, 0)
	}
}

func (m *mapServices)Run(){
	fmt.Println("没有该服务。。。")
}

func (m *mapServices) GetService(name string) server{
	switch name {
	case "mysql":
		if s,ok m.services[name];ok{
			return s
		} else {
			// 这里也可以初始化
			m.services[name] = &mysql{} // 对象注册
			return &mysql{}
		}
		
	case "redis":
		if s,ok m.services[name];ok{
			return s
		} else {
			m.services[name] = &redis{} // 对象注册 这里缺少初始化 也就是上面的一些列的参数
			// 那么可将上面的Connection 方法修改成传参建立连接即可
			// 如果不理解的话，
			return &redis{}
		}
	default:
		return &mapServices{}
	}
}

//请看这
// type DB interface{
// 	Connect(sql,url,username,password,port string) bool
// }
// // 在把对应的指针接受方法修改成
// func (*mysql)Connect(sql,url,username,password,port string) bool {
// 	// 建立连接....
// 	fmt.Println("建立mysql连接.....")
// 	return true
// }
// 然后就可以了，得到服务之后建立服务连接，输入自己的参数和数据
// 优点： 可以拓展，有新的服务，可以添加，然后添加服务连接对象
// 缺点: 会越来越复杂越来越庞大 
// 不做实现演示， 自己可以尝试一下锻炼一下自己
// --------------------------------------------------------------------------


func main(){
 // 无状态解耦
//   say := NewApiSay("1")
//   // 直接可以接口来实现
//   say.Say("小猪")
//   // 如下
//   say2 := NewApiSay("2")
//   // 说明根据传参的不同实例化的对象不同,而实例化不同对象的流程就是工厂生产对象的流程
//   // 然后不同的对象都实现了抽象接口的方法，这个就是接口的设计规则那么，就可以无状态解耦的调用接口方法来实现实例的方法
//   say2.Say("小明")
// Output:
// 小猪 is say hi....
// 小明 is say hello....

// --------------------------------场景1-----------------------------------
	// OK 上面已经完成  下面来调用看看
	// car := NewCar("BenChi") // 传入一个想要的车
	// car.Run()

	// car1 := NewCar("Plane") // 传入一个非法的
	// car1.Run()
// OutPut:
// 	benchi is running
// 	这里是生产汽车的。。。。
// --------------------------------场景2-----------------------------------
	// 实现如下: 参数不同的情况实例对象不同，不同的对象有自己不同的方法
	// db := NewJDBC("mysql","localhost","admin","1234","3306")
	// db.Connect() // 连接成功之后就可以做一些db 自己的方法
// OutPut:
// 	建立mysql连接.....
// --------------------------------场景3-----------------------------------


}